/**
 * Version 14 mrt 2008 - initial version
 */
package audioreaper.core;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import audioreaper.machine.DefaultMachineContext;
import audioreaper.machine.MachineContext;
import audioreaper.machine.Master;


/**
 * <code>ReaperSystem</code>.
 *
 * @author <a href="mailto:vincent@nazarene.nl">Vincent van Beveren</a>
 *
 */
public class ReaperSystem implements Runnable {

	private MachineContext _context;
	private Master _master;
	private int[] _buffer;
	private byte[] _audioBuffer;
	private Thread _audioProcessor;
	private AudioFormat _format;
	private SourceDataLine _line;	
	private volatile boolean _stop;
	
	public ReaperSystem() {
		_context = new DefaultMachineContext();
		// get the master buffer
		_master = _context.getMaster();
		_buffer = _master.getBuffer();
		assert _context.getBitsPerSample() == 16;
		assert _context.getChannelConfig().getNrOfChannels() == 2;
		_audioBuffer = new byte[_buffer.length * 2];	// assumes 16bits per pixel
		_format = new AudioFormat(
				_context.getSampleRate(), 
				_context.getBitsPerSample(), 
				_context.getChannelConfig().getNrOfChannels(),
				true,
				true);
	}
	
	public AudioFormat getFormat() {
		return _format;
	}
	
	/**
	 * Initializes the audio system with the specified dataline. May be an
	 * audio file, or the audiocard.
	 */
	public synchronized void init(SourceDataLine line) {
		if (_audioProcessor != null) {
			// TODO maybe throw exception
			return;			
		}
		_line = line;
		_audioProcessor = new Thread(this, "AudioProcessor");
		_audioProcessor.start();
	}


	public void deinit() throws InterruptedException {
		synchronized (this) {
			if (_audioProcessor == null) {
				return;			
			}
		}
		_stop = true;
		System.out.println("Shutting down...");
		_audioProcessor.join();
		System.out.println("Completed");
	}
	
	/**
	 * @return the context
	 */
	public MachineContext getContext() {
		return _context;
	}
	
	/**
	 * Walks through the processing sequence.
	 */
	private void process() {
		_master.render();		// render the audio content
		convertBuffer();		// convert the reaper buffer to an buffer for the audio system
		writeBufferToLine();	// send it to the audio system
	}
	
	/**
	 * Converts the simple reaper buffer to the actual audio output buffer.
	 */
	private void convertBuffer() {
		int b = 0;
		for (int i = 0; i < _buffer.length; i++) {
			b = _buffer[i];
			// peek processing
			if (b < -32000) {
				b = -32000;
			} else if (b > 32000) {
				b = 32000;
			}
			_audioBuffer[i << 1] = (byte)(b >> 8);
			_audioBuffer[(i << 1) + 1] = (byte)(b & 0xff);
		}
	}

	private void writeBufferToLine() {
		int r = _line.write(_audioBuffer, 0, _audioBuffer.length);
		while (r < _audioBuffer.length) {
			r += _line.write(_audioBuffer, r, _audioBuffer.length - r);
		}
	}

	private void initAudio() throws ReaperException {
		try {
			_line.open(_format);
			_line.start();
		} catch (LineUnavailableException e) {
			throw new ReaperException("Failed to init line", e);
		}
	}
	
	private void deinitAudio() {
		_line.stop();
		_line.close();		
	}
	
	@Override
	public void run() {
		try {
			initAudio();
			
			while (!_stop) {
				process();
			}
		} catch (RuntimeException re) {
			System.err.println("ReaperSystem died!");
			re.printStackTrace();
		} catch (ReaperException re) {
			System.err.println("ReaperSystem died!");
			re.printStackTrace();
		} finally {
			synchronized (this) {
				_audioProcessor = null;
				_stop = true;
			}
			deinitAudio();
			_line = null;
		}
	}
	
}
